﻿
using System.ComponentModel;
using System.Diagnostics.Contracts;

namespace System.Reflection
{
    public class Method<TObject, TResult>
    {
        // Fields
        private MethodInfo f_MethodInfo;
        private string f_Name;
        private TObject f_Object;
        private bool f_Private;

        private Func<object[], TResult> f_Method;

        // Methods
        public string Name
        {
            get { return f_Name; }
            set
            {
                Contract.Requires(!string.IsNullOrEmpty(value));
                Initialize(f_Object, f_Name = value, f_Private);
            }
        }

        public TObject Object
        {
            get { return f_Object; }
            set { Initialize(f_Object = value, f_Name, f_Private); }
        }

        // Properties

        public bool Private
        {
            get { return f_Private; }
            set { Initialize(f_Object, f_Name, f_Private = value); }
        }

        public Method(TObject o, string Name, bool Private = false)
        {
            Contract.Requires(!string.IsNullOrEmpty(Name));
            Initialize(f_Object = o, f_Name = Name, f_Private = Private);
        }

        private void Initialize(TObject o, string Name, bool Private)
        {
            Contract.Requires(Name != null);
            Contract.Requires(Name != "");

            var IsPublic = Private ? BindingFlags.NonPublic : BindingFlags.Public;
            var IsStatic = o == null ? BindingFlags.Static : BindingFlags.Instance;

            var type = typeof(TObject);
            if(type == typeof(object) && o != null)
                type = o.GetType();

            f_MethodInfo = type.GetMethod(Name, IsStatic | IsPublic);

            f_Method = o != null && o is ISynchronizeInvoke
                ? (Func<object[], TResult>)(Args => (TResult)((ISynchronizeInvoke)o).Invoke((Func<object[], TResult>)PrivateInvoke, new object[] { Args }))
                : PrivateInvoke;
        }

        public TResult Invoke(params object[] Args) { return f_Method(Args); }

        private TResult PrivateInvoke(params object[] Args) { return (TResult)f_MethodInfo.Invoke(f_Object, Args); }


    }
}